/**
 * 通过class关键字定义类
 *      类的属性定义：
 *          [属性名]:[属性类型] = [默认值]
 *      可以使用static开头定义静态属性（类属性），读取时，直接通过类名获取，实例对象访问不了
 */
class Person{
    readonly name:string = 'live'
    age:number = 18
    hobby:string = '我爱打水'
    plan:string = '明天打球'
    static sex:string = '男'

    /**
     * 构造函数：实例化对象时，自动执行
     * 构造函数中的this表示当前的实例对象
     */
    constructor(age?:number, plan?:string){
        // console.log(this)
        if(age){
            this.age = age
        }
        if(plan){
            this.plan = plan
        }
    }

    /**
     * 方法的定义（无法访问静态属性）
     * 若方法名前加 static，则该方法为类方法，通过类名访问，且方法里只能访问静态属性
     */
    setPlan():string{
        return this.plan
    }
}
let oneStudent = new Person()
// console.log(oneStudent)  //看不到sex属性，因为sex是静态属性（类属性）
// oneStudent.age = 22
// console.log(oneStudent)
// console.log(Person.sex) //通过类名直接读取
// oneStudent.name = 'Reset'  //name为只读属性，不可修改
// console.log(oneStudent.sex) //实例对象读取不了，会报错
// Person.setPlan()
console.log('oneStudent plan: ', oneStudent.setPlan())
let missZhang = new Person(14, '熟悉ts')
console.log(missZhang)
console.log('missZhang plan: ', missZhang.setPlan())


/**
 * abstract关键字：用来定义抽象类，抽象类和其它类的区别：抽象类不能用来实例化对象
 *                抽象类就是专门用来被继承的类
 *       同时，也可以用来定义抽象方法，抽象方法没有方法体，且只能定义在抽象类中，子类
 *       必须对抽象方法进行重写
 */

abstract class Life{
    name:string = 'xx'
    age:number = 1    
    constructor(name?:string, age?:number){
        if(name){
            this.name = name
        }
        if(age){
            this.age = age
        }
    }
    abstract callFn():void
}

/**
 * 若子类和父类都存在相同的方法，则子类的方法会覆盖父类的方法，这叫方法重写
 * 在类的方法中，super表示当前类的父类，常用于子类构造函数的执行中且
 * 必须是子类的构造函数中的第一句
 */
class Dog extends Life{
    langage:string = '狗语'
    constructor(name?:string ,age?:number, langage?:string){
        super(name,age)
        if(langage){  
            // super(name, age)         
            this.langage = langage
        }
    }
    eatFn():void{
        console.log(`${this.name} is eating！`)
    }
    callFn(): void {
        // super.callFn()
        console.log(`${this.name} is 汪汪汪！`)
    }
}

let dog = new Dog('点点', 6)
console.log(dog)
dog.callFn()
dog.eatFn()

/**
 * 接口：用来定义一个类接口，用来定义一个类中应该包含哪些属性和方法，与类的描述相似
 *      不过它可以重复，而类的描述不行
 *       同时接口也可以当成类型声明去使用
 */
type infoData = {
    name: string,
    hobby:string
}
//不能重复
// type infoData = {
//     age: number
// }

interface info{
    name: string,
    hobby: string,
}
// 可以重复但里面的属性不能重复
interface info{
    age: number
}

//合并了所有
let infoData:info = {
    name: 'thc',
    hobby: 'learn',
    age: 26
}


/**
 * 接口可以在定义类的时候去限制类的结构，类似于抽象类
 *      但接口中的所有属性都不能有实际值
 *      接口只定义对象的结构，而不考虑实际值，而抽象类中可以有实际值
 * 接口可以说是一种规范，只有实现了这一规范，符合了，才能用
 */
interface Pepole{
    name:string    
    speakFn():string
}
class Hero implements Pepole{
    name: string = 'thc' //因为加了implements Pepole, 必须实例化 Pepole中的所有属性或方法
    language:string = 'cn'
    constructor(name?:string){
        if(name){
            this.name = name
        }
    }
    speakFn(): string {
        return '我爱打水'
        console.log('我爱打水')
    }
    learnLan(): string{
        return 'speak chinese'
        // console.log()
    }
}
let hero = new Hero()
/**
 * 类属性的封装
 *      默认情况下，类（class）中的属性是可以任意的被修改，但在开发时，如果随时任意被修改，显得非常不严谨、不安全
 *      此时，需要对属性进行一定程度的封装（修饰）
 *      1. 可以在属性前面添加修饰符
 *          public: 公有属性，通过其修饰的属性可以在任意位置访问修改，属性不加修饰符，默认是 public
 *          private: 私有属性，只能在类的内部进行访问修改,这里的类是指当前类，在子类里并不能访问修改
 *              通过方法对属性进行访问修改
 *              也可以通过属性的存取器（getter、setter）来进行访问修改
 *          protected: 保护属性，只能在当前类和当前类的子类中进行访问修改
 *          
 */

class Phone{
    name: string
    protected size:number
    private price:number
    constructor(name:string, size:number, price: number){
        this.name = name
        this.size = size
        this.price = price
    }
    //方法实现
    getPrice(){
        return this.price
    }
    setPrice(price){
        this.price = price
    }
    // 属性存取器实现
    get price_(){
        return this.price
    }
    set price_(price){
        this.price = price
    }

}
let apple = new Phone('apple', 128, 8888)
apple.setPrice(6888)
console.log(apple.getPrice())
apple.price_ = 5888
console.log(apple.price_)

class Huawei extends Phone{
    get size_(){
        return this.size
    }
    // get  price_(): number {
    //     return this.price   
    // }
}
let huaweiPhone = new Huawei('华为', 128, 5688)
console.log(huaweiPhone.size_)

/**
 * extends和implements的区别
 * 1. 在类的声明中，通过关键字extends来创建一个类的子类
 *    一个类可通过关键字implements声明自己使用一个或多个接口  //例如：class Car implements Alarm, Light{}
 *    extends是继承某个类，继承之后可以使用父类的方法，也可以重写父类的方法
 *    implements是实现多个接口，接口的方法一般为空，必须重写才能使用      
 */